/*
 * @Author: DaTree
 * @Date: 2021-09-03 22:13:02
 * @LastEditTime: 2021-09-07 15:58:53
 * @LastEditors: DaTree
 * @Description: DaTreeEdit
 * @FilePath: \demo_services\static\index.js
 * 天将降大任于是人也，必先苦其心志，劳其筋骨，饿其体肤，空乏其身，行拂乱其所为。 ——《孟子•告子下》
 * Copyright 2021 DaTree
 */
let myVideoStream = null;
let myVideo = null;

var singnalingChannel, key, id,
  haveLocalMedia = false,
  connected = false,
  weWaited = false,
  yourVideoStream, yourVideo,
  doNothing = function () { },
  pc,
  constraints = {
    mandatory: {
      OfferToReceiveAudio: true,
      OfferToReceiveVideo: false
    }
  };

window.onload = () => {
  // 如果URI中提供了密钥，则自动连接信令通道
  if (queryparams && queryparams['key']) {
    document.getElementById('key').value = queryparams['key'];
    connect();
  }
  myVideo = document.querySelector("#myVideo")
  yourVideo = document.querySelector("#yourVideo")
  getMedia();
  //如果建立连接，connect()将调用createPC()
  // 如果createPC 和getMedia 成功执行，将调用 attachMedia();
}

if (navigator.mediaDevices === undefined) {
  navigator.mediaDevices = {};
}
if (navigator.mediaDevices.getUserMedia === undefined) {
  navigator.mediaDevices.getUserMedia = function (constraints) {//首先，如果有getUserMedia的话，就获得它
    const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;//一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
    if (!getUserMedia) {
      return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
    }
    //否则，为老的navigator.getUserMedia方法包裹一个Promise
    return new Promise(function (resolve, reject) {
      getUserMedia.call(navigator, constraints, resolve, reject);
    });

  }
}
const getMedia = () => {
  navigator.mediaDevices.getUserMedia({
    "audio": true,
    // "video": true
  }).then(gotUserMedia).catch(didntGetUserMedia);
}
const gotUserMedia = (stream) => {
  myVideoStream = stream;
  //有本地媒体
  haveLocalMedia = true;
  //展示本地视频
  attachMediaStream(myVideo, myVideoStream);
  // 等待建立信令通道；
  verifySetupDone();
  // 等待RTCpeerConnection创建完毕
  attachMediaIfReady();
}
const didntGetUserMedia = (e) => {
  log("Couldn't get video : " + e.message);
}
function log (str) {
  document.querySelector("#log").innerHTML = str
}
function attachMediaStream (video, stream) {
  if ("srcObject" in video) {
    video.srcObject = stream;
  } else {
    //防止在新的浏览器里使用它，应为它已经不再支持了
    video.src = window.URL.createObjectURL(stream);
  }
}


/**
 * 建立信令通道
 * 此示例程序会连接至Web服务器并建立信令通道。
 * 当出现文档负载或用户点击“Connect”按钮时，将自动调用这个示例程序
 */
function connect () {
  var errorCB, scHandlers, handleMsg;
  // 首先，获取用于连接的密钥
  key = document.getElementById("key").value;
  // 此处理程序用于处理通过信令通道收到的所有消息
  handleMsg = function (msg) {
    //首先，我们整理消息并将其发布到屏幕上
    var msgE = document.querySelector("#inmessages");
    var msgString = JSON.stringify(msg);
    msgE.value = msgString + "\n" + msgE.value;
    //随后，我们基于消息类型执行操作
    if (msg.type === "offer") {
      pc.setRemoteDescription(new RTCSessionDescription(msg));
      answer();
    } else if (msg.type === "answer") {
      pc.setRemoteDescription(new RTCSessionDescription(msg));
    } else if (msg.type === "candidate") {
      pc.addIceCandiate(new RTCIceCandidate({
        sdpMLineIndex: msg.mlineindex,
        candiate: msg.candidate
      }))
    }
  }
  // 用于信令通道的处理程序
  scHandlers = {
    'onWaiting': function () {
      setStatus('Waiting');
      // 后面将使用weWaited执行自动调用
      weWaited = true;
    },
    onConnected: function () {
      connected = true;
      setStatus("Connected");
      // 等待本地媒体就绪
      verifySetupDone();
      // 由于我们已经成功连接，因此建立RTC对等连接
      createPC();
    },
    onMessage: handleMsg
  }
  // 最后，创建信令通道
  singnalingChannel = createSignalingChannel(key, scHandlers);
  errorCB = function (msg) {
    document.querySelector("response").innerHTML = msg;
  }
  // 进行连接
  singnalingChannel.connect(errorCB);
}

// 此例程通过信令通道发送消息，其方式有2中：一是执行显示调用；二是通过用户点击“Send”按钮。
function send (msg) {
  var handler = function (res) {
    document.querySelector('#response').innerHTML = res;
    return;
  }
  // 如果没有传入，则获取消息
  msg = msg || document.querySelector('#message').value;
  // 发布到屏幕上
  msgE = document.querySelector('#outmessages');
  var msgString = JSON.stringify(msg);
  msgE.value = msgString + "\n" + msgE.value;

  // 并通过信令通道发送
  singnalingChannel.send(msg, handler);
}

/**
 * 此守护例程实际上用于对两项异步活动
 * 的完成时间进行同步：一是创建信令通道；
 * 二是获取本地媒体。
 */
function verifySetupDone () {
  // 如果信令通道已经就绪，并且我们已获得本地媒体，则继续处理。
  if (connected && haveLocalMedia) {
    setStatus('Set up');
  }
}

/**
 * 基于应用程序的进度更改ui
 * 让用户答题了解浏览器在建立信令通道和获取本地媒体方面的进度。
 */
// 此函数将通过隐藏、显示和填充各种UI元素,
// 让用户大体了解浏览器在建立信令通道、
// 获取本地媒体、创建对等连接
// 以及实际连接媒体（呼叫）方面的进度。
function setStatus (str) {
  var statuslineE = document.querySelector("#statusline"),
    statusE = document.querySelector('#status'),
    sendE = document.querySelector('#send'),
    connectE = document.querySelector('#connect'),
    callE = document.querySelector('#call'),
    scMessageE = document.querySelector('#scMessage');
  switch (str) {
    case 'Waiting':
      statuslineE.style.display = 'inline';
      statusE.innerHTML = "Waiting for peer signaling connection";
      sendE.style.display = "none";
      connectE.style.display = "none";
      break;
    case 'Connected':
      statuslineE.style.display = "inline";
      statusE.innerHTML = "Peer signaling connected,waiting for local media";
      sendE.style.display = "inline";
      connectE.style.display = "none";
      scMessageE.style.display = "inline-block";
      break;
    case 'Set up':
      statusE.innerHTML = "Peer signaling connected and local media obtained";
      break;
    case 'Ready for call':
      statusE.innerHTML = "Ready for call";
      callE.style.display = "inline";
      break;
    case 'On call':
      statusE.innerHTML = "On call";
      callE.style.display = "none";
      break;
    default:
      break;
  }
}

/**
 * 建立RTC对等连接
 */

function createPC () {
  var stunuri = true,
    turnuri = false,
    myfalse = function (v) {
      return (v === '0') || (v === "false") || (!v);
    },
    config = new Array();
  //基于个个查询参数调整配置字符串
  if (queryparams) {
    if ('stunuri' in queryparams) {
      stunuri = !myfalse(queryparams['stunuri']);
    }
    if ('turnuri' in queryparams) {
      turnuri = !myfalse(queryparams['turnuri']);
    }
  }
  // if (stunuri) {
  //   //这是Google得一台公共STUN服务器
  //   config.push({
  //     url: "stun:stun.1.google.com:19302"
  //   });
  // }
  if (turnuri) {
    if (stunuri) {
      // 这里不能使用仅支持TURN的TURN服务器，因为Chrome中存在一个bug，
      // 可导致STUN服务器响应遭到忽略，
      // 因此我们使用同时具备STUN功能的TURN服务器
      config.push({
        "url": "turn:turn-only.webrtcbook.com",
        "username": "user",
        "credential": "test"
      });
    }
  }
  console.log("config = " + JSON.stringify(config));
  pc = new RTCPeerConnection({ iceServers: config });
  pc.onicecandidate = onIceCandidate;
  pc.onaddstream = onRemoteStreamAdded;
  pc.onremovestream = onRemoteStreamRemoved;
  // 等待本地媒体准备就绪
  attachMediaIfReady();
}
// 如果我们的浏览器有另一个候选项，则将其发送给对等端
function onIceCandidate (e) {
  if (e.candidate) {
    send({
      type: 'candiate',
      mlineindex: e.candidate.sdpMLineIndex,
      candidate: e.candidate.candidate
    })
  }
}
// 如果我们的浏览器检测到了另一端添加了媒体流，则将其显示在屏幕上
function onRemoteStreamAdded (e) {
  yourVideoStream = e.stream;
  attachMediaStream(yourVideo, yourVideoStream);
  setStatus("On call");
}
/**
 * 没错，如果远端移除该流，我们不执行任何操作。
 * 毕竟这是一个 简单 的演示
 */
function onRemoteStreamRemoved (e) {
  console.log("远端移除流事件触发", e);
}

/**
 * 将本地媒体附加到对等连接
 */

// 此守护例程实际上用于对两项异步活动的完成时间进行同步
// 一是创建对等连接；二是获取本地媒体
function attachMediaIfReady (params) {
  // 如果 RTCPeerConnection 已经就绪
  // 并且我们已经获得本地媒体，则继续处理。
  if (pc && haveLocalMedia) {
    attachMedia();
  }
}

// 此例程将我们的本地媒体流添加至对等连接
// 请注意，这不会导致任何媒体开始流动
// 其作用只是指示浏览器在其下一个SDP描述中加入此流
function attachMedia () {
  pc.addStream(myVideoStream);
  setStatus("Ready for call");
  // 如果URI中call参数的值表示true，则自动执行调用，
  // 但是还要确保我们已经完成连接之前的所有步骤（提高两端已一切就绪的概率）
  if (queryparams && queryparams['call'] && !weWaited) {
    call();
  }
}

/**
 * 呼叫和应答
 */

//为offer生成会话描述
function call () {
  pc.createOffer(gotDescription, doNothing, constraints);
}
//为answer生成会话描述
function answer () {
  pc.createAnswer(gotDescription, doNothing, constraints);
}
// 无论在哪一种情况下，一旦我们获取会话描述，
// 就指示我们的浏览器将其用做本地描述，
// 然后将其发送给另一端的浏览器
// 只有先设置了本地描述，浏览器才能发送媒体并准备从另一端接受媒体
function gotDescription (localDesc) {
  debugger
  pc.setLocalDescription(localDesc);
  send(localDesc);
}

